
/* rest_description.cc
   Mathieu Stefani, 27 février 2016
   
   Example of how to use the Description mechanism
*/

#include <pistache/http.h>
#include <pistache/description.h>
#include <pistache/endpoint.h>

#include <pistache/serializer/rapidjson.h>

using namespace std;
using namespace Pistache;

namespace Generic {

void handleReady(const Rest::Request&, Http::ResponseWriter response) {
  response.send(Http::Code::Ok, "1");
}

}

class BankerService {
public:
  BankerService(Address addr)
    : httpEndpoint(std::make_shared<Http::Endpoint>(addr))
    , desc("Banking API", "0.1")
  { }

  void init(size_t thr = 2) {
    auto opts = Http::Endpoint::options()
      .threads(thr)
      .flags(Tcp::Options::InstallSignalHandler);
    httpEndpoint->init(opts);
    createDescription();
  }

  void start() {
    router.initFromDescription(desc);

    Rest::Swagger swagger(desc);
    swagger
      .uiPath("/doc")
      .uiDirectory("/home/octal/code/web/swagger-ui-2.1.4/dist")
      .apiPath("/banker-api.json")
      .serializer(&Rest::Serializer::rapidJson)
      .install(router);

    httpEndpoint->setHandler(router.handler());
    httpEndpoint->serve();
  }

  void shutdown() {
    httpEndpoint->shutdown();
  }

private:
  void createDescription() {
    desc
      .info()
      .license("Apache", "http://www.apache.org/licenses/LICENSE-2.0");

    auto backendErrorResponse =
      desc.response(Http::Code::Internal_Server_Error, "An error occured with the backend");

    desc
      .schemes(Rest::Scheme::Http)
      .basePath("/v1")
      .produces(MIME(Application, Json))
      .consumes(MIME(Application, Json));

    desc
      .route(desc.get("/ready"))
      .bind(&Generic::handleReady)
      .response(Http::Code::Ok, "Response to the /ready call")
      .hide();

    auto versionPath = desc.path("/v1");

    auto accountsPath = versionPath.path("/accounts");

    accountsPath
      .route(desc.get("/all"))
      .bind(&BankerService::retrieveAllAccounts, this)
      .produces(MIME(Application, Json), MIME(Application, Xml))
      .response(Http::Code::Ok, "The list of all account");

    accountsPath
      .route(desc.get("/:name"), "Retrieve an account")
      .bind(&BankerService::retrieveAccount, this)
      .produces(MIME(Application, Json))
      .parameter<Rest::Type::String>("name", "The name of the account to retrieve")
      .response(Http::Code::Ok, "The requested account")
      .response(backendErrorResponse);

    accountsPath
      .route(desc.post("/:name"), "Create an account")
      .bind(&BankerService::createAccount, this)
      .produces(MIME(Application, Json))
      .consumes(MIME(Application, Json))
      .parameter<Rest::Type::String>("name", "The name of the account to create")
      .response(Http::Code::Ok, "The initial state of the account")
      .response(backendErrorResponse);

    auto accountPath = accountsPath.path("/:name");
    accountPath.parameter<Rest::Type::String>("name", "The name of the account to operate on");

    accountPath
      .route(desc.post("/budget"), "Add budget to the account")
      .bind(&BankerService::creditAccount, this)
      .produces(MIME(Application, Json))
      .response(Http::Code::Ok, "Budget has been added to the account")
      .response(backendErrorResponse);

  }

  void retrieveAllAccounts(const Rest::Request& req, Http::ResponseWriter response) {
    response.send(Http::Code::Ok, "No Account");
  }

  void retrieveAccount(const Rest::Request& req, Http::ResponseWriter response) {
    response.send(Http::Code::Ok, "The bank is closed, come back later");
  }

  void createAccount(const Rest::Request& req, Http::ResponseWriter response) {
    response.send(Http::Code::Ok, "The bank is closed, come back later");
  }

  void creditAccount(const Rest::Request& req, Http::ResponseWriter response) {
    response.send(Http::Code::Ok, "The bank is closed, come back later");
  }

  std::shared_ptr<Http::Endpoint> httpEndpoint;
  Rest::Description desc;
  Rest::Router router;
};

int main(int argc, char *argv[]) {
  Port port(9080);

  int thr = 2;

  if (argc >= 2) {
    port = std::stol(argv[1]);

    if (argc == 3)
      thr = std::stol(argv[2]);
  }

  Address addr(Ipv4::any(), port);

  cout << "Cores = " << hardware_concurrency() << endl;
  cout << "Using " << thr << " threads" << endl;

  BankerService banker(addr);

  banker.init(thr);
  banker.start();

  banker.shutdown();
}
